/*
 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.management.remote.rmi;


import com.sun.jmx.remote.security.MBeanServerFileAccessController;
import com.sun.jmx.remote.internal.IIOPHelper;
import com.sun.jmx.remote.util.ClassLogger;
import com.sun.jmx.remote.util.EnvHelp;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer;

import javax.management.remote.JMXConnectionNotification;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.MBeanServerForwarder;

import javax.naming.InitialContext;
import javax.naming.NamingException;

/**
 * <p>A JMX API connector server that creates RMI-based connections
 * from remote clients.  Usually, such connector servers are made
 * using {@link javax.management.remote.JMXConnectorServerFactory
 * JMXConnectorServerFactory}.  However, specialized applications can
 * use this class directly, for example with an {@link RMIServerImpl}
 * object.</p>
 *
 * @since 1.5
 */
public class RMIConnectorServer extends JMXConnectorServer {

  /**
   * <p>Name of the attribute that specifies whether the {@link
   * RMIServer} stub that represents an RMI connector server should
   * override an existing stub at the same address.  The value
   * associated with this attribute, if any, should be a string that
   * is equal, ignoring case, to <code>"true"</code> or
   * <code>"false"</code>.  The default value is false.</p>
   */
  public static final String JNDI_REBIND_ATTRIBUTE =
      "jmx.remote.jndi.rebind";

  /**
   * <p>Name of the attribute that specifies the {@link
   * RMIClientSocketFactory} for the RMI objects created in
   * conjunction with this connector. The value associated with this
   * attribute must be of type <code>RMIClientSocketFactory</code> and can
   * only be specified in the <code>Map</code> argument supplied when
   * creating a connector server.</p>
   */
  public static final String RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE =
      "jmx.remote.rmi.client.socket.factory";

  /**
   * <p>Name of the attribute that specifies the {@link
   * RMIServerSocketFactory} for the RMI objects created in
   * conjunction with this connector. The value associated with this
   * attribute must be of type <code>RMIServerSocketFactory</code> and can
   * only be specified in the <code>Map</code> argument supplied when
   * creating a connector server.</p>
   */
  public static final String RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE =
      "jmx.remote.rmi.server.socket.factory";

  /**
   * <p>Makes an <code>RMIConnectorServer</code>.
   * This is equivalent to calling {@link #RMIConnectorServer(
   *JMXServiceURL, Map, RMIServerImpl, MBeanServer)
   * RMIConnectorServer(directoryURL,environment,null,null)}</p>
   *
   * @param url the URL defining how to create the connector server. Cannot be null.
   * @param environment attributes governing the creation and storing of the RMI object.  Can be
   * null, which is equivalent to an empty Map.
   * @throws IllegalArgumentException if <code>url</code> is null.
   * @throws MalformedURLException if <code>url</code> does not conform to the syntax for an RMI
   * connector, or if its protocol is not recognized by this implementation. Only "rmi" and "iiop"
   * are valid when this constructor is used.
   * @throws IOException if the connector server cannot be created for some reason or if it is
   * inevitable that its {@link #start() start} method will fail.
   */
  public RMIConnectorServer(JMXServiceURL url, Map<String, ?> environment)
      throws IOException {
    this(url, environment, (MBeanServer) null);
  }

  /**
   * <p>Makes an <code>RMIConnectorServer</code> for the given MBean
   * server.
   * This is equivalent to calling {@link #RMIConnectorServer(
   *JMXServiceURL, Map, RMIServerImpl, MBeanServer)
   * RMIConnectorServer(directoryURL,environment,null,mbeanServer)}</p>
   *
   * @param url the URL defining how to create the connector server. Cannot be null.
   * @param environment attributes governing the creation and storing of the RMI object.  Can be
   * null, which is equivalent to an empty Map.
   * @param mbeanServer the MBean server to which the new connector server is attached, or null if
   * it will be attached by being registered as an MBean in the MBean server.
   * @throws IllegalArgumentException if <code>url</code> is null.
   * @throws MalformedURLException if <code>url</code> does not conform to the syntax for an RMI
   * connector, or if its protocol is not recognized by this implementation. Only "rmi" and "iiop"
   * are valid when this constructor is used.
   * @throws IOException if the connector server cannot be created for some reason or if it is
   * inevitable that its {@link #start() start} method will fail.
   */
  public RMIConnectorServer(JMXServiceURL url, Map<String, ?> environment,
      MBeanServer mbeanServer)
      throws IOException {
    this(url, environment, (RMIServerImpl) null, mbeanServer);
  }

  /**
   * <p>Makes an <code>RMIConnectorServer</code> for the given MBean
   * server.</p>
   *
   * @param url the URL defining how to create the connector server. Cannot be null.
   * @param environment attributes governing the creation and storing of the RMI object.  Can be
   * null, which is equivalent to an empty Map.
   * @param rmiServerImpl An implementation of the RMIServer interface, consistent with the protocol
   * type specified in <var>url</var>. If this parameter is non null, the protocol type specified by
   * <var>url</var> is not constrained, and is assumed to be valid. Otherwise, only "rmi" and "iiop"
   * will be recognized.
   * @param mbeanServer the MBean server to which the new connector server is attached, or null if
   * it will be attached by being registered as an MBean in the MBean server.
   * @throws IllegalArgumentException if <code>url</code> is null.
   * @throws MalformedURLException if <code>url</code> does not conform to the syntax for an RMI
   * connector, or if its protocol is not recognized by this implementation. Only "rmi" and "iiop"
   * are recognized when <var>rmiServerImpl</var> is null.
   * @throws IOException if the connector server cannot be created for some reason or if it is
   * inevitable that its {@link #start() start} method will fail.
   * @see #start
   */
  public RMIConnectorServer(JMXServiceURL url, Map<String, ?> environment,
      RMIServerImpl rmiServerImpl,
      MBeanServer mbeanServer)
      throws IOException {
    super(mbeanServer);

    if (url == null) {
      throw new
          IllegalArgumentException("Null JMXServiceURL");
    }
    if (rmiServerImpl == null) {
      final String prt = url.getProtocol();
      if (prt == null || !(prt.equals("rmi") || prt.equals("iiop"))) {
        final String msg = "Invalid protocol type: " + prt;
        throw new MalformedURLException(msg);
      }
      final String urlPath = url.getURLPath();
      if (!urlPath.equals("")
          && !urlPath.equals("/")
          && !urlPath.startsWith("/jndi/")) {
        final String msg = "URL path must be empty or start with " +
            "/jndi/";
        throw new MalformedURLException(msg);
      }
    }

    if (environment == null) {
      this.attributes = Collections.emptyMap();
    } else {
      EnvHelp.checkAttributes(environment);
      this.attributes = Collections.unmodifiableMap(environment);
    }

    this.address = url;
    this.rmiServerImpl = rmiServerImpl;
  }

  /**
   * <p>Returns a client stub for this connector server.  A client
   * stub is a serializable object whose {@link
   * JMXConnector#connect(Map) connect} method can be used to make
   * one new connection to this connector server.</p>
   *
   * @param env client connection parameters of the same sort that could be provided to {@link
   * JMXConnector#connect(Map) JMXConnector.connect(Map)}.  Can be null, which is equivalent to an
   * empty map.
   * @return a client stub that can be used to make a new connection to this connector server.
   * @throws UnsupportedOperationException if this connector server does not support the generation
   * of client stubs.
   * @throws IllegalStateException if the JMXConnectorServer is not started (see {@link
   * #isActive()}).
   * @throws IOException if a communications problem means that a stub cannot be created.
   **/
  public JMXConnector toJMXConnector(Map<String, ?> env) throws IOException {
    // The serialized for of rmiServerImpl is automatically
    // a RMI server stub.
    if (!isActive()) {
      throw new
          IllegalStateException("Connector is not active");
    }

    // Merge maps
    Map<String, Object> usemap = new HashMap<String, Object>(
        (this.attributes == null) ? Collections.<String, Object>emptyMap() :
            this.attributes);

    if (env != null) {
      EnvHelp.checkAttributes(env);
      usemap.putAll(env);
    }

    usemap = EnvHelp.filterAttributes(usemap);

    final RMIServer stub = (RMIServer) rmiServerImpl.toStub();

    return new RMIConnector(stub, usemap);
  }

  /**
   * <p>Activates the connector server, that is starts listening for
   * client connections.  Calling this method when the connector
   * server is already active has no effect.  Calling this method
   * when the connector server has been stopped will generate an
   * <code>IOException</code>.</p>
   *
   * <p>The behavior of this method when called for the first time
   * depends on the parameters that were supplied at construction,
   * as described below.</p>
   *
   * <p>First, an object of a subclass of {@link RMIServerImpl} is
   * required, to export the connector server through RMI:</p>
   *
   * <ul>
   *
   * <li>If an <code>RMIServerImpl</code> was supplied to the
   * constructor, it is used.
   *
   * <li>Otherwise, if the protocol part of the
   * <code>JMXServiceURL</code> supplied to the constructor was
   * <code>iiop</code>, an object of type {@link RMIIIOPServerImpl}
   * is created.
   *
   * <li>Otherwise, if the <code>JMXServiceURL</code>
   * was null, or its protocol part was <code>rmi</code>, an object
   * of type {@link RMIJRMPServerImpl} is created.
   *
   * <li>Otherwise, the implementation can create an
   * implementation-specific {@link RMIServerImpl} or it can throw
   * {@link MalformedURLException}.
   *
   * </ul>
   *
   * <p>If the given address includes a JNDI directory URL as
   * specified in the package documentation for {@link
   * javax.management.remote.rmi}, then this
   * <code>RMIConnectorServer</code> will bootstrap by binding the
   * <code>RMIServerImpl</code> to the given address.</p>
   *
   * <p>If the URL path part of the <code>JMXServiceURL</code> was
   * empty or a single slash (<code>/</code>), then the RMI object
   * will not be bound to a directory.  Instead, a reference to it
   * will be encoded in the URL path of the RMIConnectorServer
   * address (returned by {@link #getAddress()}).  The encodings for
   * <code>rmi</code> and <code>iiop</code> are described in the
   * package documentation for {@link
   * javax.management.remote.rmi}.</p>
   *
   * <p>The behavior when the URL path is neither empty nor a JNDI
   * directory URL, or when the protocol is neither <code>rmi</code>
   * nor <code>iiop</code>, is implementation defined, and may
   * include throwing {@link MalformedURLException} when the
   * connector server is created or when it is started.</p>
   *
   * @throws IllegalStateException if the connector server has not been attached to an MBean
   * server.
   * @throws IOException if the connector server cannot be started, or in the case of the {@code
   * iiop} protocol, that RMI/IIOP is not supported.
   */
  public synchronized void start() throws IOException {
    final boolean tracing = logger.traceOn();

    if (state == STARTED) {
      if (tracing) {
        logger.trace("start", "already started");
      }
      return;
    } else if (state == STOPPED) {
      if (tracing) {
        logger.trace("start", "already stopped");
      }
      throw new IOException("The server has been stopped.");
    }

    if (getMBeanServer() == null) {
      throw new IllegalStateException("This connector server is not " +
          "attached to an MBean server");
    }

    // Check the internal access file property to see
    // if an MBeanServerForwarder is to be provided
    //
    if (attributes != null) {
      // Check if access file property is specified
      //
      String accessFile =
          (String) attributes.get("jmx.remote.x.access.file");
      if (accessFile != null) {
        // Access file property specified, create an instance
        // of the MBeanServerFileAccessController class
        //
        MBeanServerForwarder mbsf;
        try {
          mbsf = new MBeanServerFileAccessController(accessFile);
        } catch (IOException e) {
          throw EnvHelp.initCause(
              new IllegalArgumentException(e.getMessage()), e);
        }
        // Set the MBeanServerForwarder
        //
        setMBeanServerForwarder(mbsf);
      }
    }

    try {
      if (tracing) {
        logger.trace("start", "setting default class loader");
      }
      defaultClassLoader = EnvHelp.resolveServerClassLoader(
          attributes, getMBeanServer());
    } catch (InstanceNotFoundException infc) {
      IllegalArgumentException x = new
          IllegalArgumentException("ClassLoader not found: " + infc);
      throw EnvHelp.initCause(x, infc);
    }

    if (tracing) {
      logger.trace("start", "setting RMIServer object");
    }
    final RMIServerImpl rmiServer;

    if (rmiServerImpl != null) {
      rmiServer = rmiServerImpl;
    } else {
      rmiServer = newServer();
    }

    rmiServer.setMBeanServer(getMBeanServer());
    rmiServer.setDefaultClassLoader(defaultClassLoader);
    rmiServer.setRMIConnectorServer(this);
    rmiServer.export();

    try {
      if (tracing) {
        logger.trace("start", "getting RMIServer object to export");
      }
      final RMIServer objref = objectToBind(rmiServer, attributes);

      if (address != null && address.getURLPath().startsWith("/jndi/")) {
        final String jndiUrl = address.getURLPath().substring(6);

        if (tracing) {
          logger.trace("start", "Using external directory: " + jndiUrl);
        }

        String stringBoolean = (String) attributes.get(JNDI_REBIND_ATTRIBUTE);
        final boolean rebind = EnvHelp.computeBooleanFromString(stringBoolean);

        if (tracing) {
          logger.trace("start", JNDI_REBIND_ATTRIBUTE + "=" + rebind);
        }

        try {
          if (tracing) {
            logger.trace("start", "binding to " + jndiUrl);
          }

          final Hashtable<?, ?> usemap = EnvHelp.mapToHashtable(attributes);

          bind(jndiUrl, usemap, objref, rebind);

          boundJndiUrl = jndiUrl;
        } catch (NamingException e) {
          // fit e in the nested exception if we are on 1.4
          throw newIOException("Cannot bind to URL [" + jndiUrl + "]: "
              + e, e);
        }
      } else {
        // if jndiURL is null, we must encode the stub into the URL.
        if (tracing) {
          logger.trace("start", "Encoding URL");
        }

        encodeStubInAddress(objref, attributes);

        if (tracing) {
          logger.trace("start", "Encoded URL: " + this.address);
        }
      }
    } catch (Exception e) {
      try {
        rmiServer.close();
      } catch (Exception x) {
        // OK: we are already throwing another exception
      }
      if (e instanceof RuntimeException) {
        throw (RuntimeException) e;
      } else if (e instanceof IOException) {
        throw (IOException) e;
      } else {
        throw newIOException("Got unexpected exception while " +
            "starting the connector server: "
            + e, e);
      }
    }

    rmiServerImpl = rmiServer;

    synchronized (openedServers) {
      openedServers.add(this);
    }

    state = STARTED;

    if (tracing) {
      logger.trace("start", "Connector Server Address = " + address);
      logger.trace("start", "started.");
    }
  }

  /**
   * <p>Deactivates the connector server, that is, stops listening for
   * client connections.  Calling this method will also close all
   * client connections that were made by this server.  After this
   * method returns, whether normally or with an exception, the
   * connector server will not create any new client
   * connections.</p>
   *
   * <p>Once a connector server has been stopped, it cannot be started
   * again.</p>
   *
   * <p>Calling this method when the connector server has already
   * been stopped has no effect.  Calling this method when the
   * connector server has not yet been started will disable the
   * connector server object permanently.</p>
   *
   * <p>If closing a client connection produces an exception, that
   * exception is not thrown from this method.  A {@link
   * JMXConnectionNotification} is emitted from this MBean with the
   * connection ID of the connection that could not be closed.</p>
   *
   * <p>Closing a connector server is a potentially slow operation.
   * For example, if a client machine with an open connection has
   * crashed, the close operation might have to wait for a network
   * protocol timeout.  Callers that do not want to block in a close
   * operation should do it in a separate thread.</p>
   *
   * <p>This method calls the method {@link RMIServerImpl#close()
   * close} on the connector server's <code>RMIServerImpl</code>
   * object.</p>
   *
   * <p>If the <code>RMIServerImpl</code> was bound to a JNDI
   * directory by the {@link #start() start} method, it is unbound
   * from the directory by this method.</p>
   *
   * @throws IOException if the server cannot be closed cleanly, or if the
   * <code>RMIServerImpl</code> cannot be unbound from the directory.  When this exception is
   * thrown, the server has already attempted to close all client connections, if appropriate; to
   * call {@link RMIServerImpl#close()}; and to unbind the <code>RMIServerImpl</code> from its
   * directory, if appropriate.  All client connections are closed except possibly those that
   * generated exceptions when the server attempted to close them.
   */
  public void stop() throws IOException {
    final boolean tracing = logger.traceOn();

    synchronized (this) {
      if (state == STOPPED) {
        if (tracing) {
          logger.trace("stop", "already stopped.");
        }
        return;
      } else if (state == CREATED) {
        if (tracing) {
          logger.trace("stop", "not started yet.");
        }
      }

      if (tracing) {
        logger.trace("stop", "stopping.");
      }
      state = STOPPED;
    }

    synchronized (openedServers) {
      openedServers.remove(this);
    }

    IOException exception = null;

    // rmiServerImpl can be null if stop() called without start()
    if (rmiServerImpl != null) {
      try {
        if (tracing) {
          logger.trace("stop", "closing RMI server.");
        }
        rmiServerImpl.close();
      } catch (IOException e) {
        if (tracing) {
          logger.trace("stop", "failed to close RMI server: " + e);
        }
        if (logger.debugOn()) {
          logger.debug("stop", e);
        }
        exception = e;
      }
    }

    if (boundJndiUrl != null) {
      try {
        if (tracing) {
          logger.trace("stop",
              "unbind from external directory: " + boundJndiUrl);
        }

        final Hashtable<?, ?> usemap = EnvHelp.mapToHashtable(attributes);

        InitialContext ctx =
            new InitialContext(usemap);

        ctx.unbind(boundJndiUrl);

        ctx.close();
      } catch (NamingException e) {
        if (tracing) {
          logger.trace("stop", "failed to unbind RMI server: " + e);
        }
        if (logger.debugOn()) {
          logger.debug("stop", e);
        }
        // fit e in as the nested exception if we are on 1.4
        if (exception == null) {
          exception = newIOException("Cannot bind to URL: " + e, e);
        }
      }
    }

    if (exception != null) {
      throw exception;
    }

    if (tracing) {
      logger.trace("stop", "stopped");
    }
  }

  public synchronized boolean isActive() {
    return (state == STARTED);
  }

  public JMXServiceURL getAddress() {
    if (!isActive()) {
      return null;
    }
    return address;
  }

  public Map<String, ?> getAttributes() {
    Map<String, ?> map = EnvHelp.filterAttributes(attributes);
    return Collections.unmodifiableMap(map);
  }

  @Override
  public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf) {
    super.setMBeanServerForwarder(mbsf);
    if (rmiServerImpl != null) {
      rmiServerImpl.setMBeanServer(getMBeanServer());
    }
  }

    /* We repeat the definitions of connection{Opened,Closed,Failed}
       here so that they are accessible to other classes in this package
       even though they have protected access.  */

  @Override
  protected void connectionOpened(String connectionId, String message,
      Object userData) {
    super.connectionOpened(connectionId, message, userData);
  }

  @Override
  protected void connectionClosed(String connectionId, String message,
      Object userData) {
    super.connectionClosed(connectionId, message, userData);
  }

  @Override
  protected void connectionFailed(String connectionId, String message,
      Object userData) {
    super.connectionFailed(connectionId, message, userData);
  }

  /**
   * Bind a stub to a registry.
   *
   * @param jndiUrl URL of the stub in the registry, extracted from the <code>JMXServiceURL</code>.
   * @param attributes A Hashtable containing environment parameters, built from the Map specified
   * at this object creation.
   * @param rmiServer The object to bind in the registry
   * @param rebind true if the object must be rebound.
   **/
  void bind(String jndiUrl, Hashtable<?, ?> attributes,
      RMIServer rmiServer, boolean rebind)
      throws NamingException, MalformedURLException {
    // if jndiURL is not null, we nust bind the stub to a
    // directory.
    InitialContext ctx =
        new InitialContext(attributes);

    if (rebind) {
      ctx.rebind(jndiUrl, rmiServer);
    } else {
      ctx.bind(jndiUrl, rmiServer);
    }
    ctx.close();
  }

  /**
   * Creates a new RMIServerImpl.
   **/
  RMIServerImpl newServer() throws IOException {
    final boolean iiop = isIiopURL(address, true);
    final int port;
    if (address == null) {
      port = 0;
    } else {
      port = address.getPort();
    }
    if (iiop) {
      return newIIOPServer(attributes);
    } else {
      return newJRMPServer(attributes, port);
    }
  }

  /**
   * Encode a stub into the JMXServiceURL.
   *
   * @param rmiServer The stub object to encode in the URL
   * @param attributes A Map containing environment parameters, built from the Map specified at this
   * object creation.
   **/
  private void encodeStubInAddress(
      RMIServer rmiServer, Map<String, ?> attributes)
      throws IOException {

    final String protocol, host;
    final int port;

    if (address == null) {
      if (IIOPHelper.isStub(rmiServer)) {
        protocol = "iiop";
      } else {
        protocol = "rmi";
      }
      host = null; // will default to local host name
      port = 0;
    } else {
      protocol = address.getProtocol();
      host = (address.getHost().equals("")) ? null : address.getHost();
      port = address.getPort();
    }

    final String urlPath = encodeStub(rmiServer, attributes);

    address = new JMXServiceURL(protocol, host, port, urlPath);
  }

  static boolean isIiopURL(JMXServiceURL directoryURL, boolean strict)
      throws MalformedURLException {
    String protocol = directoryURL.getProtocol();
    if (protocol.equals("rmi")) {
      return false;
    } else if (protocol.equals("iiop")) {
      return true;
    } else if (strict) {

      throw new MalformedURLException("URL must have protocol " +
          "\"rmi\" or \"iiop\": \"" +
          protocol + "\"");
    }
    return false;
  }

  /**
   * Returns the IOR of the given rmiServer.
   **/
  static String encodeStub(
      RMIServer rmiServer, Map<String, ?> env) throws IOException {
    if (IIOPHelper.isStub(rmiServer)) {
      return "/ior/" + encodeIIOPStub(rmiServer, env);
    } else {
      return "/stub/" + encodeJRMPStub(rmiServer, env);
    }
  }

  static String encodeJRMPStub(
      RMIServer rmiServer, Map<String, ?> env)
      throws IOException {
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    ObjectOutputStream oout = new ObjectOutputStream(bout);
    oout.writeObject(rmiServer);
    oout.close();
    byte[] bytes = bout.toByteArray();
    return byteArrayToBase64(bytes);
  }

  static String encodeIIOPStub(
      RMIServer rmiServer, Map<String, ?> env)
      throws IOException {
    try {
      Object orb = IIOPHelper.getOrb(rmiServer);
      return IIOPHelper.objectToString(orb, rmiServer);
    } catch (RuntimeException x) {
      throw newIOException(x.getMessage(), x);
    }
  }

  /**
   * Object that we will bind to the registry.
   * This object is a stub connected to our RMIServerImpl.
   **/
  private static RMIServer objectToBind(
      RMIServerImpl rmiServer, Map<String, ?> env)
      throws IOException {
    return RMIConnector.
        connectStub((RMIServer) rmiServer.toStub(), env);
  }

  private static RMIServerImpl newJRMPServer(Map<String, ?> env, int port)
      throws IOException {
    RMIClientSocketFactory csf = (RMIClientSocketFactory)
        env.get(RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE);
    RMIServerSocketFactory ssf = (RMIServerSocketFactory)
        env.get(RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE);
    return new RMIJRMPServerImpl(port, csf, ssf, env);
  }

  private static RMIServerImpl newIIOPServer(Map<String, ?> env)
      throws IOException {
    return new RMIIIOPServerImpl(env);
  }

  private static String byteArrayToBase64(byte[] a) {
    int aLen = a.length;
    int numFullGroups = aLen / 3;
    int numBytesInPartialGroup = aLen - 3 * numFullGroups;
    int resultLen = 4 * ((aLen + 2) / 3);
    final StringBuilder result = new StringBuilder(resultLen);

    // Translate all full groups from byte array elements to Base64
    int inCursor = 0;
    for (int i = 0; i < numFullGroups; i++) {
      int byte0 = a[inCursor++] & 0xff;
      int byte1 = a[inCursor++] & 0xff;
      int byte2 = a[inCursor++] & 0xff;
      result.append(intToAlpha[byte0 >> 2]);
      result.append(intToAlpha[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
      result.append(intToAlpha[(byte1 << 2) & 0x3f | (byte2 >> 6)]);
      result.append(intToAlpha[byte2 & 0x3f]);
    }

    // Translate partial group if present
    if (numBytesInPartialGroup != 0) {
      int byte0 = a[inCursor++] & 0xff;
      result.append(intToAlpha[byte0 >> 2]);
      if (numBytesInPartialGroup == 1) {
        result.append(intToAlpha[(byte0 << 4) & 0x3f]);
        result.append("==");
      } else {
        // assert numBytesInPartialGroup == 2;
        int byte1 = a[inCursor++] & 0xff;
        result.append(intToAlpha[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
        result.append(intToAlpha[(byte1 << 2) & 0x3f]);
        result.append('=');
      }
    }
    // assert inCursor == a.length;
    // assert result.length() == resultLen;
    return result.toString();
  }

  /**
   * This array is a lookup table that translates 6-bit positive integer
   * index values into their "Base64 Alphabet" equivalents as specified
   * in Table 1 of RFC 2045.
   */
  private static final char intToAlpha[] = {
      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
      'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
      'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
      'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
  };

  /**
   * Construct a new IOException with a nested exception.
   * The nested exception is set only if JDK {@literal >= 1.4}
   */
  private static IOException newIOException(String message,
      Throwable cause) {
    final IOException x = new IOException(message);
    return EnvHelp.initCause(x, cause);
  }

  // Private variables
  // -----------------

  private static ClassLogger logger =
      new ClassLogger("javax.management.remote.rmi", "RMIConnectorServer");

  private JMXServiceURL address;
  private RMIServerImpl rmiServerImpl;
  private final Map<String, ?> attributes;
  private ClassLoader defaultClassLoader = null;

  private String boundJndiUrl;

  // state
  private static final int CREATED = 0;
  private static final int STARTED = 1;
  private static final int STOPPED = 2;

  private int state = CREATED;
  private final static Set<RMIConnectorServer> openedServers =
      new HashSet<RMIConnectorServer>();
}
